from django.conf import settings
from django.utils.encoding import iri_to_uri
from django.core.cache import cache
from django.http import HttpResponse, HttpResponseBadRequest
from django.utils.cache import patch_response_headers

def positive(value):
    try:
        ret = int(value)
        if ret <= 0:
            raise ValueError()
    except ValueError:
        raise
    return ret 

def get_attr(obj, attr, default=None):
    """Recursive get object's attribute. May use dot notation.

    >>> class C(object): pass
    >>> a = C()
    >>> a.b = C()
    >>> a.b.c = 4
    >>> get_attr(a, 'b.c')
    4
    """    
    if '.' not in attr:
        return getattr(obj, attr, default)    
    else:
        L = attr.split('.')        
        return get_attr(getattr(obj, L[0], default), '.'.join(L[1:]), default)
    
class BadRequestError(Exception):
    pass    

class AjaxService(object):
    """
    Usage:
    service = AjaxService(Model.objects.all(), ['id','email'], 'email__istartswith')
    service1 = AjaxService(Model.objects.all(), ['id','field1','fk.field1',...], 'full_name__icontains', required=['q'])
    eervice2 = AjaxService(Model.objects.all(), ['id','field1'], 'full_name__icontains', "field3='f'", required=['q'])
    
    in urls.py
        (r'^ajax/test_service/', 'service')  
        
    
    Parameters:
    
            fields       - fields to return
            filter       - master filter rule, must be accepted by filter method of queryset
            required     - list of required (also empty) parameters
            extra filter - comma separated hardcoded extra filter 
    
    Examples:
        service = AjaxService(Model.objects.all(), ['id','email'], 'email__istartswith')

    """
    LIMIT = 10
    def __init__(self, queryset, fields, filter, extrafilter=None, required=[], distinct=False, **kwargs ):
        self.fields = fields
        self.distinct = distinct
        self.filter = filter
        self.extrafilter =  (extrafilter and dict([ a.split("=") for a in extrafilter.split(",")])) or {}
        self.limit = AjaxService.LIMIT
        self.queryset = queryset # do not remove is used by ajax widgets
        self.timeout = 60
        self.required = required
        self.__name__ = '<AjaxService: (%s)>' % str(queryset._as_sql()[0])
        
        for k,v in kwargs.items():
            setattr(self, k, v )

    def iter_results(self, results, fields):
        if results:
            for r in results:
                l = []  
                for f in fields:
                    v = get_attr(r, f)
                    if callable(v):
                        v = v()
                    l.append( str( v ) )
                yield  "|".join( l ) + "\n"
    
    def _get_filters(self, request):
        def subst(k):
            if k in ('false', 'False', False):
                return 0
            elif k in ('none', 'None'):
                return None
            elif k in ('',):
                return "__any__"
            elif k in ('true', 'True', True):
                return 1
            else:
                return k
            
        ret = {}
        ret.update(self.extrafilter)
        
        self.limit = AjaxService.LIMIT
        
        for k,value in request.GET.items():
            v = subst(value)
            if k == "q":
                k = self.filter
            
            if v == "__any__":
                continue
            elif v == None:
                ret["%s__isnull" % str(k) ] = True
            elif k == "timestamp":
                #TODO handle cache
                pass
            elif k == "limit":
                self.limit = v
            else:
                ret[str(k)] = str(v)
                
        return ret
    
    def learn_cache_key(self, request, cache_timeout=None, key_prefix=None):
        if key_prefix is None:
            key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX
        if cache_timeout is None:
            cache_timeout = settings.CACHE_MIDDLEWARE_SECONDS
        entry = 'views.ajax.cache.%s.%s.%s' % (iri_to_uri(request.path), 
                                            "-".join(request.GET.values()),
                                            "-".join(self.fields)
                                            )
        return entry    
    
    def _check_required(self, request):
        if not set(request.GET.keys()).issuperset( self.required ):
            raise BadRequestError('Missing Required arguments %s' % self.required )
       
    def handle(self, request):
        try:
#            if not request.is_ajax():
#                raise BadRequestError('need ajax request')
            
            self._check_required(request)             
            key = self.learn_cache_key(request)
            response = cache.get(key, None)
            if response is None:
                filters = self._get_filters(request)
                foos = self.queryset.filter(**filters)[:positive(self.limit)]   
                #return HttpResponse(foos._as_sql() )
                if self.distinct:
                    l = set(self.iter_results(foos, self.fields))
                else:
                    l = self.iter_results(foos, self.fields)
             
                c = ''.join(l)                
                response = HttpResponse(c, mimetype='text/plain')
                response['sql'] = foos._as_sql()
                response.skip_debug_response = True
                patch_response_headers(response, self.timeout)
                cache.set(key, response, self.timeout)
            
        except ValueError, e:
            response = HttpResponseBadRequest('410: %s' % str(e))                
        except BadRequestError, e:
            response = HttpResponseBadRequest('411: %s' % str(e))
        except Exception, e:
            raise
            #response = HttpResponseBadRequest('412: %s' % e.message)            
        return response  
                
    def __call__(self, request):
        return self.handle(request)

    def __str__(self):
        return self.__name__
        